/**
 *  JSimProd - Java simulation code for production processes
 *  Copyleft Jorge Mira Yagüe <jorge.mira.yague@gmail.com>
 *  Universidad de Alicante 2009
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */
package lib.model;

import lib.xml.XMLizable;
import lib.exceptions.InvalidLengthException;
import lib.exceptions.OverlappedIntervalException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;

/**
 * A StationCalendar consists in a collection of time intervals which cannot be
 * overlapped. The class allows to add and remove intervals and checks if a
 * interval is contained or overlapped by any interval from the collection.
 * @see Interval
 * @author Jorge Mira Yagüe
 */
public class StationCalendar implements XMLizable {

    private List<Interval> intervals;

    /**
     * Builds an StationCalendar
     */
    public StationCalendar() {
        intervals = new ArrayList<Interval>();
    }

    /**
     * Adds an interval to the calendar.
     * @param interval the interval to add to the calendar
     * @return true (as specified by Collection.add)
     */
    public boolean addInterval(Interval interval)
            throws OverlappedIntervalException {
        if (overlaps(interval)) {
            throw new OverlappedIntervalException();
        }
        return intervals.add(interval);
    }

    /**
     * Removes an interval from the calendar.
     * @param interval the interval to be removed
     * @return true if the calendar contains the specified interval
     */
    public boolean removeInterval(Interval interval) {
        return intervals.remove(interval);
    }

    public void removeInterval(int index) {
        intervals.remove(index);
    }

    public List<Interval> getIntervals() {
        return intervals;
    }

    public void addIntervals(List<Interval> intervals)
            throws OverlappedIntervalException {
        int[] positions = new int[intervals.size()];
        int position = 0;

        for (int i = 0; i < positions.length; i++) {
            if (!this.intervals.isEmpty()) {
                position = indexOf(intervals.get(i));
                if (position != 0 &&
                        this.intervals.get(position - 1).overlaps(intervals.get(i))) {
                    throw new OverlappedIntervalException();
                } else if (position < this.intervals.size() &&
                        this.intervals.get(position).overlaps(intervals.get(i))) {
                    throw new OverlappedIntervalException();
                }
            }
            positions[i] = position;
        }
        for (int i = 0; i < positions.length; i++) {
            position = positions[i] + i;
            if (position < this.intervals.size()) {
                this.intervals.add(position, intervals.get(i));
            } else {
                this.intervals.add(intervals.get(i));
            }
        }

    }

    private int indexOf(Interval interval) {
        return indexOf(interval.getStart());
    }

    private int indexOf(Calendar calendar) {
        if (intervals.size() == 0) {
            return 0;
        }
        int start = 0;
        int end = intervals.size() - 1;
        int middle = 0;
        while (start <= end) {
            middle = (end + start) / 2;
            if (calendar.after(intervals.get(middle).getStart())) {
                start = middle + 1;
            } else if (calendar.before(intervals.get(middle).getStart())) {
                end = middle - 1;
            } else {
                break;
            }
        }
        return start > end ? start : middle;
    }

    /**
     * Returns true if any interval in the calendar contains the specified date
     * @param calendar date whose presence in this calendar is to be test
     * @return true if any interval contains the specified date
     */
    public boolean contains(Calendar calendar) {
        int position = 0;
        if (!this.intervals.isEmpty()) {
            position = indexOf(calendar);
            if (position != 0 &&
                    this.intervals.get(position - 1).contains(calendar)) {
                return true;
            } else if (position != intervals.size() &&
                    this.intervals.get(position).contains(calendar)) {
                return true;
            }
        }
        return false;
    }

    /**
     * Returns true if any interval in the calendar contains the specified
     * interval
     * @param interval interval whose presence in this calendar is to be test
     * @return true if any interval in the calendar contains the specified
     * interval
     */
    public boolean contains(Interval interval) {
        int position = 0;
        if (!this.intervals.isEmpty()) {
            position = indexOf(interval);
            if (position != 0 &&
                    this.intervals.get(position - 1).contains(interval)) {
                return true;
            } else if (position != intervals.size() &&
                    this.intervals.get(position).contains(interval)) {
                return true;
            }

        }
        return false;
    }

    /**
     * Returns true if any interval in the calendar overlaps the specified
     * interval
     * @param interval interval whose presence in this calendar is to be test
     * @return true if any interval in the calendar overlaps the specified
     * interval
     */
    public boolean overlaps(Interval interval) {
        int position = 0;
        if (!this.intervals.isEmpty()) {
            position = indexOf(interval);
            if (position != 0 &&
                    this.intervals.get(position - 1).overlaps(interval)) {
                return true;
            } else if (position != intervals.size() &&
                    this.intervals.get(position).overlaps(interval)) {
                return true;
            }

        }
        return false;
    }

    /**
     * Returns the soonest date on which an interval fits cointained in an
     * interval in the calendar, or null in case the interval doesn't fit in
     * any interval in the calendar
     * @param interval the interval to check if fits in any of the intervals
     * in the calendar
     * @return the soonest date on which an interval fits cointained in
     * an interval in the calendar, or null in case the interval doesn't fit in
     * any interval in the calendar
     */
    public Calendar nextContainerSlot(
            Interval interval) {
        for (Interval aux : intervals) {
            if (aux.contains(interval)) {
                return aux.getStart().before(interval.getStart())
                        ? interval.getStart()
                        : aux.getStart();
            }

        }
        return null;

    }

    /**
     * Returns the soonest end date on which an interval fits unoverlapped for any
     * interval in the calendar
     * @param interval the interval to check if fits in any of the gaps between
     * intervals in the calendar
     * @return the soonest end date on which an interval fits unoverlapped for any
     * interval in the calendar
     */
    public Calendar nextNonOverlappingSlot(
            Interval interval) {
        Interval tmpInterval = new Interval(interval);
        Calendar tmpStart = (Calendar) interval.getStart().clone();
        int position;

        if (intervals.size() == 0) {
            return (Calendar) interval.getEnd().clone();
        }

        position = indexOf(interval);


        position =
                indexOf(interval);
        if (position == 0) {
            if (!intervals.get(0).overlaps(interval)) {
                return (Calendar) interval.getEnd().clone();
            } else {
                tmpStart = (Calendar) intervals.get(position).getEnd().clone();
                tmpStart.add(Calendar.MILLISECOND, 1);
                try {
                    tmpInterval = new Interval(tmpStart, interval.Length());
                } catch (InvalidLengthException ile) {
                }
                position++;
            }

        } else if (position == intervals.size()) {
            if (!intervals.get(intervals.size() - 1).overlaps(interval)) {
                return (Calendar) interval.getEnd().clone();
            }

        } else if (intervals.get(position - 1).overlaps(interval)) {
            tmpStart = (Calendar) intervals.get(position).getEnd().clone();
            tmpStart.add(Calendar.MILLISECOND, 1);
            try {
                tmpInterval = new Interval(tmpStart, interval.Length());
            } catch (InvalidLengthException ile) {
            }
        }

        while (position < intervals.size() &&
                !intervals.get(position).overlaps(tmpInterval)) {
            tmpStart = (Calendar) intervals.get(position).getEnd().clone();
            tmpStart.add(Calendar.MILLISECOND, 1);
            try {
                tmpInterval = new Interval(tmpStart, interval.Length());
            } catch (InvalidLengthException ile) {
            }
            position++;
        }

        if (position >= intervals.size()) {
            tmpStart = (Calendar) intervals.get(
                    intervals.size() - 1).getEnd().clone();
            tmpStart.add(Calendar.MILLISECOND, 1);
            try {
                tmpInterval = new Interval(tmpStart, interval.Length());
            } catch (InvalidLengthException ile) {
            }
        }
        return tmpInterval.getEnd();
    }

    public String toXML() {
        if (intervals != null) {
            String result = "<StationCalendar>\n";
            for (Interval interval : intervals) {
                result += interval.toXML();
            }
            result += "</StationCalendar>\n";
            return result;
        }
        return "";
    }
}
